﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace DotNetRepository
{
    public class EntityEnumeration<TEntity> : IEntityEnumeration<TEntity>
        where TEntity : class, IEntity
    {
        private readonly HashSet<Guid> _entitIDs = new HashSet<Guid>();
        private readonly Expression<Func<TEntity, bool>> _predicateExpression;
        private readonly Func<TEntity, bool> _predicate;
        private readonly IEntity _container;
        private readonly int _refreshInterval;
        private DateTime? _lastRefresh;

        private bool Refresh{
            get
            {
                if (_lastRefresh == null)
                    return true;

                return (_lastRefresh.Value.Subtract(DateTime.UtcNow).TotalSeconds > _refreshInterval);
            }
        }

        public EntityEnumeration(EntityEnumerationArgs<TEntity> args)
        {
            _container = args.Container;
            _predicateExpression = args.Association;
            _predicate = _predicateExpression.Compile();
            _refreshInterval = args.RefreshInterval;
        }

        private void RefreshCollection()
        {
            if (!Refresh) return;
            // If container is new, there is no need to check the database for child references
            if (_container != null && _container.Version == 0)
            {
                return;
            }

            _entitIDs.Clear();
            foreach (var entity in Repository<TEntity>.Where(_predicateExpression))
                _entitIDs.Add(entity.ID);

            _lastRefresh = DateTime.UtcNow;
        }

        public void OnEntityUpdated(IEntity entityUpdated)
        {
            var entity = entityUpdated as TEntity;
            if (entity == null) return; // unable to cast entity
            if (_predicate(entity))
                _entitIDs.Add(entity.ID);
            else
                _entitIDs.Remove(entity.ID);
        }

        public void OnEntityDeleted(Guid entityID)
        {
            _entitIDs.Remove(entityID);
        }

        public IEnumerator<TEntity> GetEnumerator()
        {
            RefreshCollection();
            return _entitIDs.Select(Repository<TEntity>.GetByID).GetEnumerator();
        }

        public TEntity this [Guid id]
        {
            get
            {
                return _entitIDs.Contains(id) ? Repository<TEntity>.GetByID(id) : null;
            }
        }

        public int RecordCount
        {
            get
            {
                RefreshCollection();
                return _entitIDs.Count;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
